import 'dart:async';
import 'dart:io';

import 'package:background_downloader/background_downloader.dart';
import 'package:install_plugin/install_plugin.dart';
import 'package:path_provider/path_provider.dart';
import 'package:top_foundation/utils/ex/string.dart';
import 'package:whale/helper/route/route_navigator.dart';
import 'package:whale/helper/utils/httputils/http_man.dart';
import 'package:whale/helper/utils/permisson.dart';
import 'package:whale/helper/utils/popup/index.dart';
import 'package:whale/helper/utils/util.dart';

import 'state.dart';

//ios info 配置域名

class DownloadController {
  static DownloadController instance = DownloadController._interval();
  factory DownloadController() => instance;
  DownloadController._interval();

  final state = DownloadState();

  StreamSubscription? subscription;

  Future<void> initialize() async {
    // await FileDownloader().trackTasks();
    FileDownloader().configureNotification(
      running: TaskNotification(Ls.download, 'file: {filename}'),
      progressBar: true,
    );

    FileDownloader().configure(globalConfig: [
      (Config.requestTimeout, const Duration(seconds: 100)),
    ], androidConfig: [
      (Config.useCacheDir, Config.whenAble),
    ], iOSConfig: [
      (Config.localize, {'Cancel': 'StopIt'}),
    ]).then((result) => Get.log('Configuration result = $result'));
    FileDownloader().registerCallbacks(
        taskNotificationTapCallback: myNotificationTapCallback);
    if (subscription != null) {
      subscription?.cancel();
    }

    subscription = FileDownloader().updates.listen((update) {
      final m = instance.state.list.firstWhereOrNull(
          (element) => element.task.taskId == update.task.taskId);
      if (update is TaskStatusUpdate) {
        m?.updateStatus(update);
      }
      if (update is TaskProgressUpdate) {
        m?.updateProgress(update);
      }
      saveAllModel();
    });
  }

  void myNotificationTapCallback(Task task, NotificationType notificationType) {
    print('Tapped notification $notificationType for taskId ${task.taskId}');
  }

  Future<List<DownloadFileModel>?> getAllData() async {
    if (state.data.isNotEmpty) {
      return [...state.data];
    }
    await getAllModel();
    return [...state.data];
  }

  Future<void> anyway(DownloadModel model) async {
    switch (model.status.value) {
      case TaskStatus.waitingToRetry:
        await DownloadController.instance.retry(model);
        break;
      case TaskStatus.enqueued:
        await DownloadController.instance.pause(model);
        break;
      case TaskStatus.running:
        await DownloadController.instance.pause(model);
        break;
      case TaskStatus.complete:
        open(model);
        break;
      case TaskStatus.failed:
        await DownloadController.instance.retry(model);
        break;
      case TaskStatus.canceled:
        await download(model.httpUri, model.dirName);
        break;
      case TaskStatus.paused:
        await DownloadController.instance.resume(model);
        break;
      case TaskStatus.notFound:
        await download(model.httpUri, model.dirName);
        break;
    }
  }

  Future<void> _downAnyway(DownloadModel model) async {
    switch (model.status.value) {
      case TaskStatus.waitingToRetry:
        await DownloadController.instance.retry(model);
        break;
      case TaskStatus.enqueued:
        break;
      case TaskStatus.running:
        break;
      case TaskStatus.complete:
        open(model);
        break;
      case TaskStatus.failed:
        await DownloadController.instance.retry(model);
        break;
      case TaskStatus.canceled:
        await _download(model.httpUri, model.dirName);
        break;
      case TaskStatus.paused:
        await DownloadController.instance.resume(model);
        break;
      case TaskStatus.notFound:
        await _download(model.httpUri, model.dirName);
        break;
    }
    Get.log('_downAnyway-----${state.list.length}');
  }

  Future<bool> resume(DownloadModel model) async {
    return FileDownloader().resume(model.task);
  }

  Future<bool> pause(DownloadModel model) async {
    final res = await FileDownloader().pause(model.task);
    return res;
  }

  Future<void> pauseAll() async {
    for (var e in state.list) {
      await pause(e);
    }
  }

  Future<void> pauseArr(List<DownloadModel> list) async {
    for (var e in list) {
      await pause(e);
    }
  }

  Future<void> cancel(DownloadModel model) async {
    await FileDownloader().cancelTaskWithId(model.task.taskId);
  }

  Future<void> cancelAll() async {
    await FileDownloader()
        .cancelTasksWithIds(state.list.map((e) => e.task.taskId).toList());
  }

  void open(DownloadModel model) async {
    if (model.task.filename.isAPKFileName) {
      if (Platform.isIOS) {
        await FileDownloader().openFile(task: model.task);
      } else {
        await InstallPlugin.installApk(await model.task.filePath());
      }
    } else if (model.task.filename.isVideoName ||
        model.task.filename.isImageFileName) {
      gallery([
        GalleryModel(id: model.task.taskId, url: await model.task.filePath())
      ]);
    } else {
      _urlOpen(model);
    }
  }

  void _urlOpen(DownloadModel model) async {
    if (model.status.value == TaskStatus.complete) {
      final filePath = await model.task.filePath();
      Get.toWeb(url: filePath, title: model.task.displayName);
    } else {
      Get.toWeb(url: model.httpUri, title: model.task.displayName);
    }
  }

  Future<void> retry(DownloadModel model) async {
    download(model.httpUri, model.task.directory);
  }

  Future<void> remove(
    DownloadFileModel file,
    DownloadModel model, {
    bool update = false,
  }) async {
    await cancel(model);

    model.status.value = TaskStatus.notFound;
    file.data?.remove(model);
    if (file.data?.isNotEmpty != true) {
      state.data.remove(file);
    }
    state.list.remove(model);
    saveAllModel();
    removeCache(model);
    state.valuesChanged.value = [...state.list];
  }

  Future<void> removeFile(DownloadFileModel file) async {
    if (file.data?.isNotEmpty != true) {
      return;
    }

    var count = file.data!.length;
    for (var i = 0; i < count; i++) {
      await cancel(file.data![i]);
      state.list.remove(file.data![i]);
    }

    await FileManager().delPath(file.path);
    state.data.remove(file);
    saveAllModel();

    state.valuesChanged.value = [...state.list];
  }

  Future<void> removeAll() async {
    if (state.data.isEmpty) {
      return;
    }
    for (var e in state.list) {
      await cancel(e);
    }
    for (var e in state.data) {
      await FileManager().delPath(e.path);
    }
    state.data = [];
    state.list = [];
    saveAllModel();
    state.valuesChanged.value = [...state.list];
  }

  Future<void> removeArr(
      DownloadFileModel file, List<DownloadModel> list) async {
    var dirPath = list.first.task.directory;
    for (var e in list) {
      await remove(file, e);
    }

    if (File(dirPath).lengthSync() == 0) {
      await FileManager().delPath(dirPath);
    }
    state.valuesChanged.value = [...state.list];
  }

  Future<DownloadModel?> download(String uri, String dirName) async {
    // if (!(await PermissonMan.storage())) {
    //   return null;
    // }
    Get.log('download-----${state.list.length}');
    await getAllData();
    if (state.list.where((element) => element.httpUri == uri).isNotEmpty) {
      var m = state.list.firstWhere((element) => element.httpUri == uri);
      if (m.status.value == TaskStatus.complete) {
        final action = await Popup.alert(
          msg: Ls.download,
          sureText: Ls.clickToLook,
        );
        if (action == true) {
          open(m);
          return m;
        }
      } else {
        await _downAnyway(m);
        Get.log('download-----${state.list.length}');
        return m;
      }
    }

    return await _download(uri, dirName);
  }

  Future<DownloadModel> _download(String uri, String dirName) async {
    Get.log('_download----${state.list.length}');
    final url = Uri.parse(uri);

    /// define the multi-part upload task (subset of parameters shown)
    final task = DownloadTask(
        url: uri,
        filename: url.pathSegments.last,
        directory: dirName,
        displayName: url.pathSegments.last,
        allowPause: true,
        updates:
            Updates.statusAndProgress // request status and progress updates
        );

    var file =
        state.data.firstWhereOrNull((element) => element.name == dirName);
    if (file == null) {
      final filePath = await task.filePath();
      final dirPath =
          filePath.substring(0, filePath.length - task.filename.length - 1);
      file = DownloadFileModel(name: dirName, path: dirPath);
      state.data.add(file);
    }
    file.data ??= [];

    var dm = file.data?.firstWhereOrNull((element) => element.httpUri == uri);
    if (dm == null) {
      dm = DownloadModel(
        httpUri: uri,
        date: DateTime.now(),
        task: task,
        dirName: dirName,
      );
      file.data!.add(dm);
    }
    dm.task = task;

    if (state.list.where((element) => element.httpUri == uri).isEmpty) {
      state.list.add(dm);
    }
    saveAllModel();
    FileDownloader().enqueue(task);
    state.valuesChanged.value = [...state.list];
    return dm;
  }

  Future<String> tempPath({String? fileName, String? name}) async {
    ///temp才有size
    var tempDir = (await getTemporaryDirectory()).absolute.path;

    var fp = fileName?.isNotEmpty == true ? '/$fileName' : '';
    var dirPath = '$tempDir$fp';
    if (fileName?.isNotEmpty == true) {
      if (!Directory(dirPath).existsSync()) {
        await Directory(dirPath).create();
      }
    }
    var n = name?.isNotEmpty == true ? '/$name' : '';
    return '$dirPath$n';
  }

  Future<String> docPath({String? fileName, String? name}) async {
    ///temp才有size
    var tempDir = (await getApplicationDocumentsDirectory()).absolute.path;

    var fp = fileName?.isNotEmpty == true ? '/$fileName' : '';
    var dirPath = '$tempDir$fp';
    if (fileName?.isNotEmpty == true) {
      if (!Directory(dirPath).existsSync()) {
        await Directory(dirPath).create();
      }
    }
    var n = name?.isNotEmpty == true ? '/$name' : '';
    return '$dirPath$n';
  }

  int _getCacheSize(String fileName) {
    final file = Directory(fileName);
    if (!file.existsSync()) {
      return 0;
    }
    final list = file.listSync();
    var cacheDirs = list.where((element) => element is Directory);
    if (cacheDirs.isEmpty) {
      return 0;
    }

    FileSystemEntity entity = cacheDirs.first;
    while (entity is Directory) {
      entity = entity.listSync().first;
    }
    int size = 0;
    if (entity is File) {
      size = entity.lengthSync();
    }

    return size;
  }

  Future<bool> saveAllModel() {
    print(state.list.length);
    return SpUtil().setJSON(
        state.dataKeysFileName, state.list.map((e) => e.toJson()).toList());
  }

  Future<void> getAllModel() async {
    final json = SpUtil().getJSON(state.dataKeysFileName);

    if (json is List && json.isNotEmpty) {
      state.list = [];
      state.data = [];

      for (var e in json) {
        final m = DownloadModel.fromJson(e);
        state.list.add(m);

        var file =
            state.data.firstWhereOrNull((element) => element.name == m.dirName);

        if (file == null) {
          file = DownloadFileModel(name: m.dirName, path: m.dirName)
            ..data = [m];
          state.data.add(file);
        } else {
          file.data?.add(m);
        }
      }
    }
  }

  void removeCache(DownloadModel model) {
    final file = File(model.task.directory);
    if (file.existsSync()) {
      file.delete();
    }
  }

  Future<void> getTotalSize(DownloadModel model) async {
    if (model.totalSize <= 0) {
      model.totalSize = await HttpMan().getFileSizeFromUrl(model.httpUri);
    }
  }

  void dispose() {
    FileDownloader().destroy();
    subscription?.cancel();
    subscription = null;
  }
}
